﻿using System; 
using System.Collections.Generic;
using System.Security.Principal;
using System.Web;
using System.Web.Security;
using Storage;
using Storage.Exceptions;
using Storage.EntityModels;
using Stats;
using log4net;

namespace Security
{
    public class TripLoaUser
    {
        private readonly MembershipUser securityUser;
        private int ID;
        private Profile user_profile;
        private Storage.EntityModels.User user;

#if LOG
        private readonly ILog _logger = LogManager.GetLogger(typeof(TripLoaUser));
#endif
        // By Righetti.
        internal TripLoaUser(String userName)
        {
#if LOG
            _logger.Debug("TripLoaUser ctor: " + userName);
#endif
            // Security
            securityUser = Membership.GetUser();
            if (securityUser.UserName == "admin" && userName != "admin")
                securityUser = Membership.GetUser(userName);
            else if (securityUser.UserName != userName)
            {
                throw new InvalidUserNameException(
                    "Security Error : mismatching username. To get a profile of another User use getProfile() method.");
            }
            else
            {
#if LOG
                _logger.Debug("TripLoaUser ctor: inside else: " + userName);
#endif
                // Anonymizer
                user = StorageManager.Instance.GetUserByUserName(userName);
                user_profile = new Profile(user, StorageManager.Instance.GetPrivacyLevels(user.Id),
                                           PrivacyLevel.NotVisible);
                ID = user.Id;
            }
        }

        #region ANONYMIZER

        #region  PROPERTIES

        public int UserID
        {
            get { return this.ID; }
        }

        #endregion

        #region FRIENDS_METHODS

        /// <summary>
        /// Adds a new friend to the buddy list.
        /// </summary>
        /// <param name="userId">The new friend id.</param>
        public void AddFriend(int userId)
        {
            StorageManager.Instance.AddFriend(ID, userId);
            user_profile.resync_buddies();
        }

        /// <summary>
        /// Adds friends to User
        /// </summary>
        /// <param name="ids">ids of users to add as friends</param>
        public void AddFriends(List<int> ids)
        {
            StorageManager.Instance.AddFriend(ID, ids);
        }

        /// <summary>
        /// Removes a friend from the buddy list.
        /// </summary>
        /// <param name="userId">The friend's id to remove.</param>
        /// <remarks>This method is more efficent than RemoveFriend(string username)</remarks>
        public void RemoveFriend(int userId)
        {
            StorageManager.Instance.RemoveFriend(ID, userId);
            user_profile.resync_buddies();
        }

        /// <summary>
        /// Removes a friend from the buddy list.
        /// </summary>
        /// <param name="username">The friend's username to remove.</param>
        /// <remarks>The method RemoveFriend(int userId) is more efficent</remarks>
        public void RemoveFriend(string username)
        {
            int userId = StorageManager.Instance.GetUserIdByUserName(username);
            StorageManager.Instance.RemoveFriend(ID, userId);
            user_profile.resync_buddies();
        }
        
        /// <summary>
        /// Ban the specified user.
        /// </summary>
        /// <param name="userIdToBan">The ID of the user to ban</param>
        public void BanUser(int userIdToBan)
        {
            if (userIdToBan == ID)
                throw new InvalidUserNameException("Impossible to ban yourself!");
            else
            {
                StorageManager.Instance.BanUser(ID, userIdToBan);
                user_profile.resync_buddies();
            }
        }

        /// <summary>
        /// Ban the specified user.
        /// </summary>
        /// <param name="usernameToBan">The username of the user to ban</param>
        public void BanUser(string usernameToBan)
        {
            int userIdToBan = StorageManager.Instance.GetUserIdByUserName(usernameToBan);

            if (userIdToBan == ID)
                throw new InvalidUserNameException("Impossible to ban yourself!");
            else
            {
                StorageManager.Instance.BanUser(ID, userIdToBan);
                user_profile.resync_buddies();
            }
        }

        /// <summary>
        /// Unban the specified user.
        /// </summary>
        /// <param name="userIdToUnban">The ID of the user to unban</param>
        public void UnbanUser(int userIdToUnban)
        {
            StorageManager.Instance.UnbanUser(ID, userIdToUnban);
            user_profile.resync_buddies();
        }

        /// <summary>
        /// Unban the specified user.
        /// </summary>
        /// <param name="usernameToUnban">The username of the user to unban</param>
        public void UnbanUser(string usernameToUnban)
        {
            int userIdToUnban = StorageManager.Instance.GetUserIdByUserName(usernameToUnban);
            StorageManager.Instance.UnbanUser(ID, userIdToUnban);
            user_profile.resync_buddies();
        }

        /// <summary>
        /// Get the user whit an account in the passed Social Network and in TripLoa also
        /// </summary>
        /// <param name="social_network">The Social Network name</param>
        /// <param name="sn_friend_ids">List of username in the Social Network</param>
        /// <returns>Returns a list of Profiles, one for each friend of "this" with account on both "social_networks" and TripLOA.</returns>
        public List<Profile> GetFriendsIdFromSN(string social_network, List<string> sn_friend_ids)
        {
            List<Profile> profiles = new List<Profile>();

            List<int> friends_ids = StorageManager.Instance.GetFriendsIdFromSN(social_network, sn_friend_ids);

            foreach (int id in friends_ids)
                profiles.Add(GetProfile(id, PrivacyLevel.High));

            return profiles;
        }

        /// <summary>
        /// Get the user whit an account in the passed Social Network and in TripLoa also
        /// </summary>
        /// <param name="social_network">The Social Network name</param>
        /// <param name="sn_friend_ids">List of username in the Social Network</param>
        /// <returns>Returns a list of Profiles, one for each friend of "this" with account on both "social_networks" and TripLOA.</returns>
        public List<Profile> GetFriendsIdFromSNExcludingBuddies(string social_network, List<string> sn_friend_ids,int userId)
        {
            //Implemented by Licciardi & the Interopers
            List<Profile> profiles = new List<Profile>();

            List<int> friends_ids = StorageManager.Instance.GetFriendsIdFromSNExcludingBuddies(social_network, sn_friend_ids, userId);

            foreach (int id in friends_ids)
                profiles.Add(GetProfile(id, PrivacyLevel.High));

            return profiles;
        }

        #endregion FRIENDS_METHODS

        #region PROFILE_METHODS

        /// <summary>
        /// This method returns the profile of the logged user.
        /// </summary>
        /// <returns>the profile of the logged user.</returns>
        public Profile GetProfile()
        {
            user = StorageManager.Instance.GetUserByUserName(securityUser.UserName);
            user_profile = new Profile(user, StorageManager.Instance.GetPrivacyLevels(user.Id),
                                       PrivacyLevel.NotVisible);
            return user_profile;
        }

        /// <summary>
        /// This method returns the profile of the user associated to the given user ID.
        /// </summary>
        /// <param name="OtherUserID">the user ID.</param>
        /// <returns>the profile of the user associated to the given user ID.</returns>
        public Profile GetProfile(int OtherUserID)
        {
            if (ID == OtherUserID)
                return user_profile;

            return new Profile(StorageManager.Instance.GetUserById(OtherUserID),
                               StorageManager.Instance.GetPrivacyLevels(OtherUserID),
                               StorageManager.Instance.GetRelation(ID, OtherUserID));
        }

        /// <summary>
        /// This method returns the profile of the user associated to the given user ID filtered by specified privacy level.
        /// </summary>
        /// <param name="OtherUserID">the user ID.</param>
        /// <param name="level">the privacy level.</param>
        /// <returns>the profile of the user associated to the given user ID.</returns>
        private Profile GetProfile(int OtherUserID, PrivacyLevel level)
        {
            if (ID == OtherUserID)
                return user_profile;

            return new Profile(StorageManager.Instance.GetUserById(OtherUserID),
                               StorageManager.Instance.GetPrivacyLevels(OtherUserID), level);
        }

        /// <summary>
        /// This method returns the profile of the user associated to the given username.
        /// </summary>
        /// <param name="otherTripLOAuser">the username of an other TripLOA user.</param>
        /// <returns>the profile of the user associated to otherTripLOAuser.</returns>
        public Profile GetProfile(string otherTripLOAuser)
        {
            int other_id = StorageManager.Instance.GetUserIdByUserName(otherTripLOAuser);

            return this.GetProfile(other_id);
        }

        /// <summary>
        /// Set a new Privacy Level for the field
        /// </summary>
        /// <param name="fieldName">The field to change (that surely exists)</param>
        /// <param name="newLevel">The new Privacy Level</param>
        public void SetFieldPrivacyLevel(string fieldName, PrivacyLevel newLevel)
        {
            user_profile[fieldName] = newLevel;
        }

        /// <summary>
        /// Makes changes to profile persistent (lists are excluded)
        /// </summary>
        public void SaveProfile()
        {
            // obviously i need to read the profile to write it back modified...
            user = StorageManager.Instance.GetUserByUserName(securityUser.UserName);
            
            // Saves the new field value
            user.Address = user_profile.Address;
            user.AvatarPath = user_profile.AvatarPath;
            user.Birthday = user_profile.Birthday;
            user.City = user_profile.City;
            user.DrivingLicence = user_profile.DrivingLicence;
            user.Email = user_profile.Email; // saved by stored procedure..
            user.Gender = user_profile.Gender;
            user.Name = user_profile.Name;
            user.PhoneNumber = user_profile.PhoneNumber;
            user.Smoker = user_profile.Smoker;
            user.Surname = user_profile.Surname;

            // and the relative Privacy Level
            StorageManager.Instance.SetPrivacyLevels(this.ID, user_profile.FieldTable);
            user_profile.refreshLevels();

            TripLoaDataContext db = StorageManager.Instance.Entities;
            db.SubmitChanges();
        }

        #endregion PROFILE_METHODS

        #region RELATION_METHODS

        /// <summary>
        /// Set the new privacy relation between this user and the one specified as parameter
        /// </summary>
        /// <param name="userID">The ID of the other user</param>
        /// <param name="newLevel">The new privacy level</param>
        public void setRelation(int otherUserID, PrivacyLevel newLevel)
        {
            Storage.StorageManager.Instance.SetRelation(ID, otherUserID, newLevel);
        }

        /// <summary>
        /// Get the privacy relation between this user and the one specified as parameter
        /// </summary>
        /// <param name="userID">The ID of the other user</param>
        public PrivacyLevel getRelation(int otherUserID)
        {
            return Storage.StorageManager.Instance.GetRelation(ID, otherUserID);
        }

        #endregion RELATION_METHODS

        #region SUPPORT_METHODS

        /// <summary>
        /// Support method for display an anonimyzed vehicle taken from Stats Module
        /// </summary>
        /// <param name="userID">The relative profile to display</param>
        /// <returns>A value that represents the current vehicle</returns>
        public int GetUserVehicleType(Profile OtherProfile)
        {
            if (OtherProfile.Id == this.ID || OtherProfile.isVisible("Vehicles"))
            {
                return StatsModule.getUserVehicleType(OtherProfile.Id);
            }
            else return (int) StatsModule.vehicleTypes.UnknownVehicleType;
        }

        #endregion

        #endregion ANONYMIZER

        #region SECURITY

        /*
        public void refreshProfile()
        {
            user = StorageManager.Instance.GetUserByUserName(securityUser.UserName);
            user_profile = new Profile(user, StorageManager.Instance.GetPrivacyLevels(user.Id), PrivacyLevel.NotVisible);
        }*/

        /// <summary>
        /// Signs out this user.
        /// </summary>
        /// <returns>True if everything went the right way. False otherwise.</returns>
        public bool signOut()
        {
            bool success = false;
            if (TripLoaUserManager.isUserCached(UserName))
            {
                success = TripLoaUserManager.removeUser(UserName);
                //FormsAuthentication.SignOut();
            }

            return success;
        }

        /// <summary>
        /// Gets the password for the membership user from the membership data store.
        /// </summary>
        /// <returns>
        /// The password for the membership user.
        /// </returns>
        public string GetPassword()
        {
            return securityUser.GetPassword();
        }

        /// <summary>
        /// Gets the password for the membership user from the membership data store.
        /// </summary>
        /// <returns>
        /// The password for the membership user.
        /// </returns>
        /// <param name="passwordAnswer">The password answer for the membership user.</param>
        public string GetPassword(string passwordAnswer)
        {
            return securityUser.GetPassword(passwordAnswer);
        }

        /// <summary>
        /// Updates the password for the membership user in the membership data store.
        /// </summary>
        /// <returns>
        /// true if the update was successful; otherwise, false.
        /// </returns>
        /// <param name="oldPassword">The current password for the membership user.</param>
        /// <param name="newPassword">The new password for the membership user.</param>
        /// <exception cref="T:System.ArgumentException"><paramref name="oldPassword" /> is an empty string.-or-<paramref name="newPassword" /> is an empty string.</exception>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="oldPassword" /> is null.-or-<paramref name="newPassword" /> is null.</exception>
        public bool ChangePassword(string oldPassword, string newPassword)
        {
            return securityUser.ChangePassword(oldPassword, newPassword);
        }

        /// <summary>
        /// Updates the password question and answer for the membership user in the membership data store.
        /// </summary>
        /// <returns>
        /// true if the update was successful; otherwise, false.
        /// </returns>
        /// <param name="password">The current password for the membership user.</param>
        /// <param name="newPasswordQuestion">The new password question value for the membership user.</param>
        /// <param name="newPasswordAnswer">The new password answer value for the membership user.</param>
        /// <exception cref="T:System.ArgumentException"><paramref name="password" /> is an empty string.-or-<paramref name="newPasswordQuestion" /> is an empty string.-or-<paramref name="newPasswordAnswer" /> is an empty string.</exception>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="password" /> is null.</exception>
        public bool ChangePasswordQuestionAndAnswer(string password, string newPasswordQuestion,
                                                    string newPasswordAnswer)
        {
            return securityUser.ChangePasswordQuestionAndAnswer(password, newPasswordQuestion, newPasswordAnswer);
        }

        /// <summary>
        /// Resets a user's password to a new, automatically generated password.
        /// </summary>
        /// <returns>
        /// The new password for the membership user.
        /// </returns>
        /// <param name="passwordAnswer">The password answer for the membership user.</param>
        public string ResetPassword(string passwordAnswer)
        {
            return securityUser.ResetPassword(passwordAnswer);
        }

        /// <summary>
        /// Resets a user's password to a new, automatically generated password.
        /// </summary>
        /// <returns>
        /// The new password for the membership user.
        /// </returns>
        public string ResetPassword()
        {
            return securityUser.ResetPassword();
        }

        /// <summary>
        /// Clears the locked-out state of the user so that the membership user can be validated.
        /// </summary>
        /// <returns>
        /// true if the membership user was successfully unlocked; otherwise, false.
        /// </returns>
        public bool UnlockUser()
        {
            return securityUser.UnlockUser();
        }

        /// <summary>
        /// Gets the logon name of the membership user.
        /// </summary>
        /// <returns>
        /// The logon name of the membership user.
        /// </returns>
        public string UserName
        {
            get { return securityUser.UserName; }
        }

        /// <summary>
        /// Gets the user identifier from the membership data source for the user.
        /// </summary>
        /// <returns>
        /// The user identifier from the membership data source for the user.
        /// </returns>
        public object ProviderUserKey
        {
            get { return securityUser.ProviderUserKey; }
        }

        /// <summary>
        /// Gets or sets the e-mail address for the membership user.
        /// </summary>
        /// <returns>
        /// The e-mail address for the membership user.
        /// </returns>
        public string Email
        {
            get { return securityUser.Email; }
            set { securityUser.Email = value; }
        }

        /// <summary>
        /// Gets the password question for the membership user.
        /// </summary>
        /// <returns>
        /// The password question for the membership user.
        /// </returns>
        public string PasswordQuestion
        {
            get { return securityUser.PasswordQuestion; }
        }

        /// <summary>
        /// Gets or sets application-specific information for the membership user.
        /// </summary>
        /// <returns>
        /// Application-specific information for the membership user.
        /// </returns>
        public string Comment
        {
            get { return securityUser.Comment; }
            set { securityUser.Comment = value; }
        }

        /// <summary>
        /// Gets or sets whether the membership user can be authenticated.
        /// </summary>
        /// <returns>
        /// true if the user can be authenticated; otherwise, false.
        /// </returns>
        public bool IsApproved
        {
            get { return securityUser.IsApproved; }
            set { securityUser.IsApproved = value; }
        }

        /// <summary>
        /// Gets a value indicating whether the membership user is locked out and unable to be validated.
        /// </summary>
        /// <returns>
        /// true if the membership user is locked out and unable to be validated; otherwise, false.
        /// </returns>
        public bool IsLockedOut
        {
            get { return securityUser.IsLockedOut; }
        }

        /// <summary>
        /// Gets the most recent date and time that the membership user was locked out.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.DateTime" /> object that represents the most recent date and time that the membership user was locked out.
        /// </returns>
        public DateTime LastLockoutDate
        {
            get { return securityUser.LastLockoutDate; }
        }

        /// <summary>
        /// Gets the date and time when the user was added to the membership data store.
        /// </summary>
        /// <returns>
        /// The date and time when the user was added to the membership data store. 
        /// </returns>
        public DateTime CreationDate
        {
            get { return securityUser.CreationDate; }
        }

        /// <summary>
        /// Gets or sets the date and time when the user was last authenticated.
        /// </summary>
        /// <returns>
        /// The date and time when the user was last authenticated.
        /// </returns>
        public DateTime LastLoginDate
        {
            get { return securityUser.LastLoginDate; }
            set { securityUser.LastLoginDate = value; }
        }

        /// <summary>
        /// Gets or sets the date and time when the membership user was last authenticated or accessed the application.
        /// </summary>
        /// <returns>
        /// The date and time when the membership user was last authenticated or accessed the application.
        /// </returns>
        public DateTime LastActivityDate
        {
            get { return securityUser.LastActivityDate; }
            set { securityUser.LastActivityDate = value; }
        }

        /// <summary>
        /// Gets the date and time when the membership user's password was last updated.
        /// </summary>
        /// <returns>
        /// The date and time when the membership user's password was last updated.
        /// </returns>
        public DateTime LastPasswordChangedDate
        {
            get { return securityUser.LastPasswordChangedDate; }
        }

        /// <summary>
        /// Gets whether the user is currently online.
        /// </summary>
        /// <returns>
        /// true if the user is online; otherwise, false.
        /// </returns>
        public bool IsOnline
        {
            get { return securityUser.IsOnline; }
        }

        /// <summary>
        /// Gets the name of the membership provider that stores and retrieves user information for the membership user.
        /// </summary>
        /// <returns>
        /// The name of the membership provider that stores and retrieves user information for the membership user.
        /// </returns>
        public string ProviderName
        {
            get { return securityUser.ProviderName; }
        }

        /// <summary>
        /// Gets whether the user is currently booked in.
        /// </summary>
        /// <returns>
        /// true if the user is booked in; otherwise, false.
        /// </returns>
        public bool isBookedIn
        {
            get { return user.BookedIn; }
        }

        #endregion SECURITY
    }
}